1 // Ringz tuner - h. james harkins, http://www.dewdrop-world.net
2 // Makes analog-style drum sounds with a bank of tunable Ringz filters
3 // code is released under the LGPL, http://creativecommons.org/licenses/LGPL/2.1/
6 // Server.default = s = Server.internal;
10 // double-click inside the parenthesis, then hit enter to evaluate
13 var percBus, percGroup, ringzBus, ringzGroup, trigbus, trigsynth, src, toStereo,
14 levelBus, levelEditor, toStereoOut,
15 nodes, ffreqs, rqs, decays, amps,
17 freqspec, rqspec, ampspec,
18 noiseString = "PinkNoise.ar", envString = "Env.perc(0.01, 0.2)",
20 post, buildSynthDef, currentDef, g, stop,
22 window, leftview, rightview, ffreqview, rqview, ampview, rateEdit, newButton,
23 funcedit, envedit, helpview;
26 percBus = Bus.audio(s, 1);
27 ringzBus = Bus.audio(s, 1);
28 percGroup = Group.new;
29 ringzGroup = Group.after(percGroup);
30 levelBus = Bus.control(s, 1).set(0.75);
31 toStereoOut = SynthDef('1to2', { |bus, level|
32 Out.ar(0, (In.ar(bus, 1) * level) ! 2)
33 }).play(ringzGroup, [bus: ringzBus, level: levelBus.asMap], addAction: \addAfter);
35 SynthDef(\ringz, { |ffreq = 20, decay = 0.01, in, out, amp = 0|
36 Out.ar(out, Ringz.ar(In.ar(in, 1), ffreq, decay, amp))
39 nodes = Array.new(10);
43 decays = dt.(ffreqs, rqs);
44 log001 = log(0.001); // constant, will be used repeatedly
46 // func to calc decay time from freq and rq
49 (log001 / log(1 - (pi/sr * ffreq * rq))) / sr;
51 freqspec = \freq.asSpec;
52 rqspec = [1, 0.001, \exp].asSpec;
53 ampspec = \amp.asSpec;
56 window = MultiPageLayout("Ringz tuner", Rect(150, 5, 800, 570));
57 leftview = FlowView(window, 470@490);
58 rightview = FlowView(window, 280@490);
60 // left-hand side: filters
61 GUI.staticText.new(leftview, 150@20).string_("frequencies");
62 GUI.staticText.new(leftview, 150@20).string_("resonances");
63 GUI.staticText.new(leftview, 150@20).string_("amplitudes");
64 ffreqview = GUI.multiSliderView.new(leftview, 150@200)
65 .value_(freqspec.unmap(ffreqs))
68 ffreqs = freqspec.map(v.value);
69 decays = dt.(ffreqs, rqs);
71 n.set(\ffreq, ffreqs[i], \decay, decays[i])
74 rqview = GUI.multiSliderView.new(leftview, 150@200)
75 .value_(rqspec.unmap(rqs))
78 rqs = rqspec.map(v.value);
79 decays = dt.(ffreqs, rqs);
81 n.set(\decay, decays[i]);
84 ampview = GUI.multiSliderView.new(leftview, 150@200)
85 .value_(ampspec.unmap(amps))
88 amps = ampspec.map(v.value);
89 nodes.do({ |n, i| n.set(\amp, amps[i]) })
92 rateEdit = NumberEditor(2, [0.5, 10]).action_({ |val| trigsynth.set(\trigrate, val) });
93 levelEditor = NumberEditor(0.75, \amp).action_({ |val| levelBus.set(val) });
96 GUI.staticText.new(leftview, 50@20).string_("trigrate").align_(\right);
97 rateEdit.gui(leftview);
98 GUI.staticText.new(leftview, 50@20).string_("volume").align_(\right);
99 levelEditor.gui(leftview);
101 // ActionButtons -- add filter, post parameters, stop, save, load
103 (nodes.size > 0).if({
104 postf("\nKlank array:\n`[ %,\n %,\n % ]\n\n",
105 ffreqs, //[..nodes.size-1],
106 amps, // \amp.asSpec.map(ampview.value[..nodes.size-1]),
107 decays); // [..nodes.size-1];
108 [ffreqs, decays, amps]
109 .flop/*[..nodes.size-1]*/.do({ |item|
110 postf("[\\ffreq, %, \\decay, %, \\amp, %]\n", *item);
116 newButton = ActionButton(leftview, "new resonz", {
117 if(nodes.size < 10) {
118 ffreqs = ffreqs.add(20);
120 decays = dt.(ffreqs, rqs);
122 ffreqview.value = freqspec.unmap(ffreqs);
123 rqview.value = rqspec.unmap(rqs);
124 ampview.value = ampspec.unmap(amps);
125 // nodes.add(ringzMixer.play(\ringz, [\in, percMixer.inbus.index]));
126 nodes.add(Synth(\ringz, [\in, percBus, \out, ringzBus], ringzGroup,
127 addAction: \addToTail));
130 ActionButton(leftview, "post parameters", post);
138 percBus.free; percGroup.free;
139 ringzBus.free; ringzGroup.free;
141 // percMixer.free; ringzMixer.free;
142 if(window.isClosed.not) { window.onClose_(nil).close };
145 ActionButton(leftview, "stop", stop);
148 ActionButton(leftview, "save", {
149 GUI.dialog.savePanel({ |path|
150 [ffreqview.value, rqview.value, ampview.value,
151 noiseString, envString, nodes.size]
152 .writeTextArchive(path)
156 ActionButton(leftview, "load", {
158 GUI.dialog.getPaths({ |path|
159 values = Object.readTextArchive(path[0]);
160 (values.size != 6).if({
161 Error("File does not contain a ringz spec.").throw;
163 noiseString = values[3];
164 funcedit.setString(noiseString, 0, funcedit.string.size);
165 envString = values[4];
166 envedit.setString(envString, 0, envedit.string.size);
168 ffreqview.value = values[0];
169 rqview.value = values[1];
170 ampview.value = values[2];
173 Synth(\ringz, [\in, percBus, \out, ringzBus], ringzGroup,
174 addAction: \addToTail);
175 // ringzMixer.play(\ringz, [\in, percMixer.inbus.index]);
176 } ! values[5]; // make the nodes
177 { ffreqview.doAction;
186 // dynamic synthdef construction based on user input
187 GUI.staticText.new(rightview, 100@20).string_("Noise function:");
188 ActionButton(rightview, "evaluate", { |view|
189 noiseString = funcedit.string;
193 funcedit = GUI.textView.new(rightview, 275@200)
194 .string_(noiseString);
197 GUI.staticText.new(rightview, 100@20).string_("Envelope:");
198 ActionButton(rightview, "evaluate", { |view|
199 envString = envedit.string;
200 src.setn(\env, envString.interpret.asArray);
203 envedit = GUI.textView.new(rightview, 275@200)
204 .string_("Env.perc(0.01, 0.2)");
206 trigbus = Bus.control(s, 1);
207 { trigsynth = SynthDef(\percTrig, { |trigrate, out|
208 Out.kr(out, Impulse.kr(trigrate))
209 }).play(percGroup, [\trigrate, rateEdit.value, \out, trigbus], addAction: \addToHead);
214 currentDef.notNil.if({
215 s.sendMsg(\d_free, currentDef.name);
217 func = ("{ " ++ noiseString ++ " }").interpret;
218 env = envString.interpret;
219 currentDef = SynthDef(\source, { |trigbus, outbus|
220 var trig, sig, envCtl;
221 trig = In.kr(trigbus, 1);
222 // Out.kr(trigbus, trig);
223 envCtl = Control.names(\env).kr(0 ! 100); // allow 25 env nodes maximum
224 sig = SynthDef.wrap(func, nil, [trig]) * EnvGen.ar(envCtl, trig);
228 src = Synth(\source, [\trigbus, trigbus, \outbus, percBus], percGroup,
229 addAction: \addToTail);
230 src.setn(\env, env.asArray);
234 helpview = GUI.textView.new(leftview, 458@161);
235 if(GUI.scheme.name == 'CocoaGUI') {
236 helpview.font = GUI.font.new("Helvetica", 12);
238 helpview.string = "Analog percussion tuner by James Harkins
240 Move the black knobs up and down to change the resonator characteristics.
242 The noise function and envelope control the exciter -- change the code and click 'evaluate' to hear it. Click 'new resonz' to add another filter (up to 10). Filters run in parallel, not in series.
244 The 'save' button stores the specs into a file; 'load' returns the editor to a saved state. 'stop' closes the window and removes all synths.";
245 helpview.editable = false;
247 window.onClose = stop;